// ######################################################################//
// Copyright [2011-2012] Delta Air Lines, Inc./Delta Technology, Inc.    //
//                       All Rights Reserved                             //
//              Access, Modification, or Use Prohibited                  //
// Without Explicit Permission of Delta Air Lines or Delta Technology.   //
// ######################################################################//
package com.delta.rafg.core.base;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * The base class for this application.Contains an Error handler used by SOA
 * interfaces to intercept error messages and by Session interfaces to create
 * service tracking ids.
 * 
 * @author 737061
 * 
 */
public class Application {

	/*
	 * The hash map instance that maps the DLM session id with a service
	 * tracking id.
	 */
	private Map<String, String> dlmSessionMap = new HashMap<String, String>();

	private static ErrorHandler errorHandler;
	private static Application application = new Application();

	private static List<String> loggedOperations = new ArrayList<String>();

	/**
	 * Returns a singleton instance of this class.
	 * 
	 * @return
	 */
	public static Application get() {

		return application;
	}

	/**
	 * The default constructor. Discouraged to be initialized.
	 */
	private Application() {

	}

	/**
	 * Return or create a tracking id for the session id specified.
	 * 
	 * @param sessionId
	 * @return
	 */
	public String getTrackingId(final String sessionId) {

		synchronized (dlmSessionMap) {
			return this.dlmSessionMap.get(sessionId);
		}
	}

	/**
	 * creates and returns a new tracking id
	 */
	public String createTrackingId() {

		return UUID.randomUUID().toString().toUpperCase();
	}

	/**
	 * Maps the tracking id to session.
	 * 
	 */
	public void mapTrackingIdWithSession(final String sessionId,
			final String trackingId) {
		synchronized (dlmSessionMap) {
			this.dlmSessionMap.put(sessionId, trackingId);
		}
	}

	/**
	 * Delete the tracking id for the specified session.
	 * 
	 */
	public void removeTrackingId(final String sessionId) {

		this.dlmSessionMap.remove(sessionId);
	}

	/**
	 * Initialize the error handler when this class is instantiated. This error
	 * handler is used by the application when the web service operations are
	 * invoked to return back error text in readable format.
	 */
	public void setUpErrorHandler(Application.ErrorHandler handler) {
		errorHandler = handler;
	}

	/**
	 * Iterates through the error codes map returns the error text that matches
	 * the service name, operation name and the error code passed in.
	 * 
	 * @param serviceName
	 * @param operationName
	 * @param errorCode
	 * @return
	 */
	public String getError(final String serviceName,
			final String operationName, final String errorCode,
			final String defaultMessage) {
		/*
		 * DO NOT INITIALIZE THE ERROR HANDLER ALWAYS. THIS IS A WRONG WAY TO
		 * DO. ERROR HANDLER IS INITIALIZED AND SET UP WHEN THE TPF ERROR CODES
		 * ARE DOWNLOADED.THERE SHOULD BE ONE AND ONLY ONE INSTANCE OF THIS
		 * ERROR HANDLER PER AXIS APPLICATION.REFER REFDATAINTERFACE CLASS
		 * [METHOD NAME RETRIEVETPFERRORCODES TO SEE HOW THIS IS SET
		 * UP--SUDHAKAR
		 */
		// errorHandler=new Application.ErrorHandler();
		String errorMessage = errorHandler.getError(serviceName, operationName,
				errorCode);
		if (errorMessage == null
				|| ErrorHandler.DEFAULT_ERROR.equals(errorMessage)) {
			String modifiedErrorMsg = ErrorUtil.strip(defaultMessage);
			/*
			 * After refining the error messsage, check if the message is an
			 * integer. We might again need to check for the actual error
			 * message through Handler.
			 */
			try {
				int msgErrorCode = Integer.parseInt(modifiedErrorMsg);
				errorMessage = errorHandler.getError(serviceName,
						operationName, String.valueOf(msgErrorCode));

			} catch (NumberFormatException nfe) {
				// probably this is not a number.Set the error message back to
				// default message sent in.
				errorMessage = modifiedErrorMsg;
			}
		}
		return errorMessage;
	}

	/**
	 * The error handler for Axis Application.
	 * 
	 * @author 737061
	 * 
	 */
	public static class ErrorHandler {

		/**
		 * 320800 - Changing the error codes to a ConcurrentHashMap and also
		 * removing the synchnronized access to it
		 */
		// private List<String[]> errorCodes = new ArrayList<String[]>();
		private Map<String, String> errorCodes = new ConcurrentHashMap<String, String>();

		public static final String DEFAULT_ERROR = "Error Message Not Defined";

		/**
		 * The default constructor.
		 */
		public ErrorHandler() {

		}

		/**
		 * Iterates through the error codes map returns the error text that
		 * matches the service name, operation name and the error code passed
		 * in.
		 * 
		 * @param serviceName
		 * @param operationName
		 * @param errorCode
		 * @return
		 */
		public String getError(final String serviceName,
				final String operationName, String errorCode) {
			if (errorCode != null) {

				if (!errorCode.isEmpty()) {
					errorCode = errorCode.replaceFirst("^0+(?!$)", "");
				}

				return errorCodes.get(serviceName.toLowerCase() + "_"
						+ operationName.toLowerCase() + "_"
						+ errorCode.toLowerCase());

				/**
				 * 320800 - removing this low performance code - synchronized
				 * (errorCodes) { for (String[] array : errorCodes) { if
				 * (array[1].equalsIgnoreCase(serviceName) &&
				 * array[2].equalsIgnoreCase(operationName) &&
				 * array[0].equalsIgnoreCase(errorCode)) { return array[3]; } }
				 * }
				 */
			}

			if (!loggedOperations.contains(serviceName + operationName)) {
				System.err
						.println("ErrorHandler:Unable to Locate exact TPF Error:ServiceName"
								+ serviceName
								+ ":OperationName:"
								+ operationName);
				loggedOperations.add(serviceName + operationName);
			}

			// returning an empty value for now.
			return DEFAULT_ERROR;
		}

		/**
		 * 320800 - modified to initialize the ConcurrentHashmap
		 * @param serviceName
		 * @param operationName
		 * @param errorCode
		 * @param message
		 */
		public void init(final String serviceName, final String operationName,
				final String errorCode, final String message) {

			this.errorCodes.put(
					serviceName.toLowerCase() + "_"
							+ operationName.toLowerCase() + "_"
							+ errorCode.toLowerCase(), message);
		}
	}

	/**
	 * A Utility class that parses a String to identify the error message that
	 * can be displayed to the user. The returned error message can also be an
	 * interger, and that is left to the Error Handler to decide if it needs to
	 * decipher the message through the returned error code.
	 * 
	 * @author 737061
	 * @see {@link ErrorHandler}
	 * 
	 */
	private static class ErrorUtil {

		private static final String[] specialChars = { ">", "+", "]" };

		/**
		 * Strips of unwanted and special characters or a portion of String and
		 * returns a meaningful human readable message.
		 * 
		 * @param errorMessage
		 * @return
		 */
		public static String strip(String errorMessage) {
			String refinedErrorMessage = "";
			if (errorMessage != null) {
				if (errorMessage.lastIndexOf(":") != -1) {
					refinedErrorMessage = errorMessage.substring(errorMessage
							.lastIndexOf(":") + 1);
					refinedErrorMessage = refinedErrorMessage.trim();
				} else {
					refinedErrorMessage = errorMessage;
				}
				for (int i = 0; i < specialChars.length; i++) {
					if (refinedErrorMessage.contains(specialChars[i])) {
						refinedErrorMessage = refinedErrorMessage.replace(
								specialChars[i], "");
					}
				}
				/**
				 * One more time!. some error messages from SOA contains a |
				 * symbol[For example PNRList.RetrieveList. What this means is
				 * that the text preceding | symbol is a message tag and text
				 * succeeding | symbol is the error message or error code.
				 */
				if (refinedErrorMessage.contains("|")) {
					String str = refinedErrorMessage
							.substring(refinedErrorMessage.lastIndexOf("|") + 1);
					if (str == null || str.isEmpty()) {
						return refinedErrorMessage;
					}
					refinedErrorMessage = str;
				}
			}

			return refinedErrorMessage.trim();
		}
	}
}
